﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Security.Cryptography;

namespace NetworkLibrary
{
    public class Client
    {
        #region Variables
        public Socket clientSocket;
        NetworkLibrary.NetworkClient server;
        RSAParameters publicKey, privateKey;

        int port = 9050;
        byte[] command = new byte[1024]; //Contains the last command sent
        byte[] dataToBeSent;
        int dataToBeSentType;
        string clientName;
        bool connected = false;

        string dir = "C:\\";
        #endregion

        #region Events
        private event Common.OnClientConnected onConnected;
        private event Common.OnClientDisConnected onDisconnected;
        private event Common.OnShuttingDown onShuttingDown;
        private event Common.DataReceived onDataReceived;
        private event Common.Log onLog;
        private event Common.OnFileReceived onFileReceived;
        private event Common.ProgressUpdate fileProgressUpdates;
        private event Common.OnFileTransferStart onFileTransferStart;
        #endregion

        #region Accessors
        public string BsPublicKey   // added by Steve
        {
            get
            {
                RSACryptoServiceProvider t = new RSACryptoServiceProvider();
                try
                {
                    t.ImportParameters(server.PublicKey);
                }
                catch (Exception err)
                {
                }
                return t.ToXmlString(false);
            }
        }
        public bool IsConnected
        {
            get { return connected; }
            set { connected = value; }
        }
        #endregion
        /// <summary>
        /// Starts a network server to send data between server and clients
        /// Designed for the Friendly Fire Application
        /// </summary>
        /// <param name="publicKey">An RSA Public Key. Sent to clients that connect.</param>
        /// <param name="privateKey">An RSA Private Key. Used to decode messages that are received.</param>
        public Client(string HostName_IP, RSAParameters publicKey, RSAParameters privateKey, string clientName)
        {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
            this.clientName = clientName;
            string ConnectTo = GetIPAddress(HostName_IP);
            if(ConnectTo != "")
                StartClientSocket(ConnectTo);
        }

        /// <summary>
        /// Starts a network server to send data between server and clients
        /// Designed for the Friendly Fire Application
        /// </summary>
        /// <param name="publicKey">An RSA Public Key. Sent to clients that connect.</param>
        /// <param name="privateKey">An RSA Private Key. Used to decode messages that are received.</param>
        /// <param name="port">Allows the server to be started on a port other than the default (9050)</param>
        public Client(string HostName_IP, int port, RSAParameters publicKey, RSAParameters privateKey, string clientName)
        {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
            this.clientName = clientName;
            this.port = port;
            string ConnectTo = GetIPAddress(HostName_IP);
            if(ConnectTo != "")
                StartClientSocket(ConnectTo);
        }

        /// <summary>
        /// Determines if it's a DNS Entry or an IP Address
        /// Returns either the IP Address or Gets the IP Address of the DNS Entry and returns it.
        /// </summary>
        /// <param name="ConnectTo">The DNS Name or IP Address</param>
        /// <returns></returns>
        string GetIPAddress(string ConnectTo)
        {
            bool hostName = false;
            for (int i = 0; i < ConnectTo.Length; i++)
                if (ConnectTo[i] != '.')
                    if (Char.IsLetter(ConnectTo[i]))
                        hostName = true;
            if (hostName)
                try{
                    return Dns.GetHostEntry(ConnectTo).AddressList[0].ToString();
                }
                catch{
                    return ""; }
            else
                return ConnectTo;
        }

        /// <summary>
        /// Starts the client socket
        /// </summary>
        void StartClientSocket(string IP)
        {
            clientSocket = new Socket(AddressFamily.InterNetwork,
                       SocketType.Stream, ProtocolType.Tcp);
            clientSocket.LingerState = new LingerOption(true, 20);
            clientSocket.Ttl = 3;
            clientSocket.DontFragment = false;
            IPEndPoint iep = new IPEndPoint(IPAddress.Parse(IP), port);
            clientSocket.BeginConnect(iep, new AsyncCallback(Connected), clientSocket);
        }

        #region Asynchronous Calls
        /// <summary>
        /// Allows the client to connect to an end point
        /// </summary>
        /// <param name="iar"></param>
        void Connected(IAsyncResult iar)
        {
            Socket client = (Socket)iar.AsyncState;
                try
                {
                    client.EndConnect(iar);

                    //Send Nickname Length
                    Common.Send(client, clientName.Length);
                    //Send Nickname
                    Common.Send(client, clientName);

                    //Send Public Key to the connected server
                    RSACryptoServiceProvider provider2 = new RSACryptoServiceProvider();
                    provider2.ImportParameters(publicKey);
                    string xml = provider2.ToXmlString(false);
                    Common.Send(client, xml.Length);
                    Common.Send(client, xml);

                    //Receive Servers public key
                    RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
                    provider.FromXmlString(Common.ReceiveString(client, Common.ReceiveInt(client)));
                    RSAParameters t = provider.ExportParameters(false);

                    connected = true;

                    server = new NetworkClient(client, "Server", t);
                    if (onConnected != null)
                        onConnected(server);

                    //client.BeginReceive(command, 0, Common.BufferLength, SocketFlags.None, new AsyncCallback(Command), client);
                    client.BeginReceive(command, 0, 1024, SocketFlags.None, new AsyncCallback(Command), client);
                }
                catch (Exception err)
                {
                }
        }
        void Command(IAsyncResult iar)
        {
            Socket client = (Socket)iar.AsyncState;
            if (client.Connected)
            {
                try{
                int recv = client.EndReceive(iar);
                string receivedData = Encoding.ASCII.GetString(command, 0, recv);
                if (client.Connected)
                {
                    if (receivedData == "ReceiveData")
                    {
                        NetworkData tempData = Common.ReceiveNonChunkedData(server);
                        if (onDataReceived != null)
                            onDataReceived(tempData);
                        client.BeginReceive(command, 0, Common.BufferLength, SocketFlags.None, new AsyncCallback(Command), client);
                    }
                    else
                        if (receivedData == "ReceiveLargeData")
                        {
                            NetworkData tempData = Common.ReceiveData(server);
                            if (onDataReceived != null)
                                onDataReceived(tempData);
                            client.BeginReceive(command, 0, Common.BufferLength, SocketFlags.None, new AsyncCallback(Command), client);
                        }
                        else
                            if (receivedData == "ReceiveFile")
                            {
                                FileReceivedData file = Common.ReceiveFile(server, dir, fileProgressUpdates, onFileTransferStart);
                                if (onFileReceived != null)
                                    onFileReceived(file);
                                client.BeginReceive(command, 0, Common.BufferLength, SocketFlags.None, new AsyncCallback(Command), client);
                            }
                            else
                                if (receivedData == "Shutdown")
                                {
                                    server.ClientSocket.Shutdown(SocketShutdown.Both);
                                    server.ClientSocket.Disconnect(false);
                                    if (onShuttingDown != null)
                                        onShuttingDown(server.ClientSocket);
                                    server.ClientSocket = null;
                                    connected = false;
                                }
                }
                
                }
                catch (Exception err)
                {
                    
                }
            }
        }
        void SendDataAsync(IAsyncResult iar)
        {
            Socket client = (Socket)iar.AsyncState;
            if (client.Connected)
            {
                client.EndSend(iar);
                Common.SendNonChunkedData(dataToBeSent, dataToBeSentType, server);
            }
        }
        void SendLargeDataAsync(IAsyncResult iar)
        {
            Socket client = (Socket)iar.AsyncState;
            if (client.Connected)
            {
                client.EndSend(iar);
                Common.SendData(dataToBeSent, dataToBeSentType, server);
            }
        }
        void SendFileAsync(IAsyncResult iar)
        {
            Socket client = (Socket)iar.AsyncState;
            if (client.Connected)
            {
                client.EndSend(iar);
                Common.SendFile(fileToBeSent, server, fileProgressUpdates, onFileTransferStart);
            }
        }
        void ReceiveEncryptionAsync(IAsyncResult iar)
        {
            Socket client = (Socket)iar.AsyncState;
            if (client.Connected)
            {
                client.EndSend(iar);

                RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
                provider.FromXmlString(Common.ReceiveString(client, Common.ReceiveInt(client)));
                RSAParameters t = provider.ExportParameters(false);
                server.SetEncyrption(new EncryptedTransfer(privateKey, t));

                RSACryptoServiceProvider provider2 = new RSACryptoServiceProvider();
                provider2.ImportParameters(publicKey);
                string xml = provider2.ToXmlString(false);
                Common.Send(client, xml.Length);
                Common.Send(client, xml);
            }
        }
        #endregion

        #region Commands
        public bool SendData(byte[] data, int dataType)
        {
            //Set these temporary values so that the Asynchronous call can access them
            dataToBeSent = data;
            dataToBeSentType = dataType;

            if (connected)
            {
                //Tell the client to Receive Data and then start the Sending Process
                byte[] msg = Encoding.ASCII.GetBytes("ReceiveData");
                server.ClientSocket.Send(msg);
                Common.SendNonChunkedData(dataToBeSent, dataToBeSentType, server);
                //server.ClientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendDataAsync), server.ClientSocket);
                return true;
            }
            return false;
        }
        public bool SendLargeData(byte[] data, int dataType)
        {
            //Set these temporary values so that the Asynchronous call can access them
            dataToBeSent = data;
            dataToBeSentType = dataType;

            if (connected)
            {
                //Tell the client to Receive Data and then start the Sending Process
                byte[] msg = Encoding.ASCII.GetBytes("ReceiveLargeData");
                server.ClientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendLargeDataAsync), server.ClientSocket);
                return true;
            }
            return false;
        }
        string fileToBeSent = "";
        public bool SendLargeData(string file)
        {
            //Set these temporary values so that the Asynchronous call can access them
            fileToBeSent = file;

            if (connected)
            {
                //Tell the client to Receive Data and then start the Sending Process
                byte[] msg = Encoding.ASCII.GetBytes("ReceiveFile");
                server.ClientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendFileAsync), server.ClientSocket);
                return true;
            }
            return false;
        }
        public bool EncryptData()
        {
            byte[] msg = Encoding.ASCII.GetBytes("PublicKey");
            server.ClientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(ReceiveEncryptionAsync), server.ClientSocket);
            return true;
        }
        public void Shutdown()
        {
            byte[] msg = Encoding.ASCII.GetBytes("Shutdown");
            if (server.ClientSocket != null && server.ClientSocket.Connected)
            {
                server.ClientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, null, server.ClientSocket);
                clientSocket.Disconnect(false);
            }
            
            connected = false;
        }
        #endregion

        public string DownloadDirectory { get { return dir; } set { dir = value; } }

        #region Events
        /// <summary>
        /// The Event used when the Client Connects to a server
        /// </summary>
        public event Common.OnClientConnected OnConnected
        {
            add { onConnected += value; }
            remove { onConnected -= value; }
        }
        /// <summary>
        /// The Event used when the Client Disconnects from a server
        /// </summary>
        public event Common.OnClientDisConnected OnDisConnected
        {
            add { onDisconnected += value; }
            remove { onDisconnected -= value; }
        }
        /// <summary>
        /// The Event used when the Client Receives data from a server
        /// </summary>
        public event Common.DataReceived OnDataReceived
        {
            add { onDataReceived += value; }
            remove { onDataReceived -= value; }
        }
        /// <summary>
        /// The Event used when the Client Receives a file from a server
        /// </summary>
        public event Common.OnFileReceived OnFileReceived
        {
            add { onFileReceived += value; }
            remove { onFileReceived -= value; }
        }
        /// <summary>
        /// The Event used when the Client writes something to it's log
        /// Errors, Connections, Disconnects, etc. etc. are logged
        /// </summary>
        public event Common.Log OnLog
        {
            add { onLog += value; }
            remove { onLog -= value; }
        }
        /// <summary>
        /// The Event used when the client shuts down.
        /// </summary>
        public event Common.OnShuttingDown OnShuttindDown
        {
            add { onShuttingDown += value; }
            remove { onShuttingDown -= value; }
        }       
        /// <summary>
        /// The Event used when the client updates the progress of a file transfer
        /// </summary>
        public event Common.ProgressUpdate FileProgressUpdate
        {
            add { fileProgressUpdates += value; }
            remove { fileProgressUpdates -= value; }
        }
        /// <summary>
        /// The Event used when the client begins a file transfer
        /// </summary>
        public event Common.OnFileTransferStart OnFileTransferStart
        {
            add { onFileTransferStart += value; }
            remove { onFileTransferStart -= value; }
        }
        #endregion
    }
}
